using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Verse;
using RimWorld;




namespace Verse.AI{
public static class ToilFailConditions
{
	public static Toil FailOn( this Toil toil, Func<Toil, bool> condition )
	{
		toil.AddEndCondition( () => 
			{
				if( condition(toil) )
					return JobCondition.Incompletable;
				return JobCondition.Ongoing;
			});
		return toil;
	}

	public static T FailOn<T>( this T f, Func<bool> condition ) where T : IJobEndable
	{
		f.AddEndCondition( ()=>
		{
			if(condition())
				return JobCondition.Incompletable;
			return JobCondition.Ongoing;
		});
		return f;
	}

	public static T FailOnDestroyedOrNull<T>( this T f, TargetIndex ind ) where T : IJobEndable
	{
		f.AddEndCondition( ()=> 
			{
				if( f.GetActor().jobs.curJob.GetTarget( ind).Thing.DestroyedOrNull() )
					return JobCondition.Incompletable;
				return JobCondition.Ongoing;
			});
		return f;
	}

	public static T FailOnDespawnedOrNull<T>( this T f, TargetIndex ind ) where T : IJobEndable
	{
		f.AddEndCondition( ()=>
			{
				var target = f.GetActor().jobs.curJob.GetTarget(ind);
				var t = target.Thing;

				// if there is no thing but the target is valid (it's a cell) then don't end the job
				if( t == null && target.IsValid )
					return JobCondition.Ongoing;

				// note: if the target is spawned in another map, then from the actor's perspective it's unspawned, so we end the job
				if( t == null || !t.Spawned || t.Map != f.GetActor().Map )
					return JobCondition.Incompletable;

				return JobCondition.Ongoing;
			});
		return f;
	}

	public static T EndOnDespawnedOrNull<T>( this T f, TargetIndex ind, JobCondition endCondition = JobCondition.Incompletable ) where T : IJobEndable
	{
		f.AddEndCondition( ()=>
			{
				var target = f.GetActor().jobs.curJob.GetTarget(ind);
				var t = target.Thing;

				// if there is no thing but the target is valid (it's a cell) then don't end the job
				if( t == null && target.IsValid )
					return JobCondition.Ongoing;

				// note: if the target is spawned in another map, then from the actor's perspective it's unspawned, so we end the job
				if( t == null || !t.Spawned || t.Map != f.GetActor().Map )
					return endCondition;

				return JobCondition.Ongoing;
			});
		return f;
	}

	public static T FailOnDowned<T>( this T f, TargetIndex ind ) where T : IJobEndable
	{
		f.AddEndCondition( ()=>
			{
				var t = f.GetActor().jobs.curJob.GetTarget(ind).Thing;
				if( ((Pawn)t).Downed )
					return JobCondition.Incompletable;
				return JobCondition.Ongoing;
			});
		return f;
	}

	public static T FailOnNotDowned<T>( this T f, TargetIndex ind ) where T : IJobEndable
	{
		f.AddEndCondition( ()=>
			{
				var t = f.GetActor().jobs.curJob.GetTarget(ind).Thing;
				if( !((Pawn)t).Downed )
					return JobCondition.Incompletable;
				return JobCondition.Ongoing;
			});
		return f;
	}

	public static T FailOnNotAwake<T>( this T f, TargetIndex ind ) where T : IJobEndable
	{
		f.AddEndCondition( ()=>
			{
				var t = f.GetActor().jobs.curJob.GetTarget(ind).Thing;
				if( !((Pawn)t).Awake() )
					return JobCondition.Incompletable;
				return JobCondition.Ongoing;
			});
		return f;
	}

	public static T FailOnNotCasualInterruptible<T>( this T f, TargetIndex ind ) where T : IJobEndable
	{
		f.AddEndCondition( ()=>
			{
				var t = f.GetActor().jobs.curJob.GetTarget(ind).Thing;
				if( !((Pawn)t).CanCasuallyInteractNow() )
					return JobCondition.Incompletable;
				return JobCondition.Ongoing;
			});
		return f;
	}

	public static T FailOnMentalState<T>( this T f, TargetIndex ind ) where T : IJobEndable
	{
		f.AddEndCondition( ()=>
			{
				var p = f.GetActor().jobs.curJob.GetTarget(ind).Thing as Pawn;
				if( p != null && p.InMentalState )
					return JobCondition.Incompletable;
				return JobCondition.Ongoing;
			});
		return f;
	}

	public static T FailOnAggroMentalState<T>( this T f, TargetIndex ind ) where T : IJobEndable
	{
		f.AddEndCondition( ()=>
			{
				var p = f.GetActor().jobs.curJob.GetTarget(ind).Thing as Pawn;
				if( p != null && p.InAggroMentalState )
					return JobCondition.Incompletable;
				return JobCondition.Ongoing;
			});
		return f;
	}

	public static T FailOnSomeonePhysicallyInteracting<T>(this T f, TargetIndex ind) where T : IJobEndable
	{
		f.AddEndCondition( () =>
			{
				var actor = f.GetActor();
				var t = actor.jobs.curJob.GetTarget(ind).Thing;

				if( t != null
					&& actor.Map.physicalInteractionReservationManager.IsReserved(t)
					&& !actor.Map.physicalInteractionReservationManager.IsReservedBy(actor, t) )
				{
					return JobCondition.Incompletable;
				}

				return JobCondition.Ongoing;
			});

		return f;
	}

	public static T FailOnForbidden<T>( this T f, TargetIndex ind ) where T : IJobEndable
	{
		f.AddEndCondition( ()=>
		{
			Pawn actor = f.GetActor();
			if( actor.Faction != Faction.OfPlayer )
				return JobCondition.Ongoing;
			
			if( actor.jobs.curJob.ignoreForbidden )
				return JobCondition.Ongoing;

			var thing = actor.jobs.curJob.GetTarget(ind).Thing;

			if( thing == null )
				return JobCondition.Ongoing;

			if( thing.IsForbidden( actor ) )
				return JobCondition.Incompletable;

			return JobCondition.Ongoing;
		});
		return f;
	}

	public static T FailOnDespawnedNullOrForbidden<T>( this T f, TargetIndex ind ) where T : IJobEndable
	{
		f.FailOnDespawnedOrNull(ind);
		f.FailOnForbidden(ind);
		return f;
	}

	public static T FailOnDestroyedNullOrForbidden<T>( this T f, TargetIndex ind ) where T : IJobEndable
	{
		f.FailOnDestroyedOrNull(ind);
		f.FailOnForbidden(ind);
		return f;
	}

	public static T FailOnThingMissingDesignation<T>( this T f, TargetIndex ind, DesignationDef desDef ) where T : IJobEndable
	{
		f.AddEndCondition( ()=>
			{
				var actor = f.GetActor();
				var job =  actor.jobs.curJob;
				if( job.ignoreDesignations )
 					return JobCondition.Ongoing;
				if( actor.Map.designationManager.DesignationOn(job.GetTarget(ind).Thing, desDef ) == null )
					return JobCondition.Incompletable;
				return JobCondition.Ongoing;
			}
		);
		return f;
	}

	public static T FailOnCellMissingDesignation<T>( this T f, TargetIndex ind, DesignationDef desDef ) where T : IJobEndable
	{
		f.AddEndCondition( ()=>
			{
				var actor = f.GetActor();
				var job =  actor.jobs.curJob;
				if( job.ignoreDesignations )
 					return JobCondition.Ongoing;
				if( actor.Map.designationManager.DesignationAt(job.GetTarget(ind).Cell, desDef ) == null )
					return JobCondition.Incompletable;
				return JobCondition.Ongoing;
			}
		);
		return f;
	}

	public static T FailOnBurningImmobile<T>( this T f, TargetIndex ind ) where T : IJobEndable
	{
		f.AddEndCondition( ()=> 
			{
				if(f.GetActor().jobs.curJob.GetTarget(ind).ToTargetInfo(f.GetActor().Map).IsBurning() )
					return JobCondition.Incompletable;
				return JobCondition.Ongoing;
			});
		return f;
	}



	//======================================================================================
	//================================ Toil-only conditions ================================
	//======================================================================================

	public static Toil FailOnDespawnedOrForbiddenPlacedThings( this Toil toil )
	{
		toil.AddFailCondition( ()=>
            {
                if(toil.actor.jobs.curJob.placedThings == null)
                    return false;
                
        		foreach( var targ in toil.actor.jobs.curJob.placedThings )
        		{
        			if( !targ.thing.Spawned
						|| targ.thing.Map != toil.actor.Map // note: if the target is spawned in another map, then from the actor's perspective it's unspawned, so we end the job
						|| (!toil.actor.CurJob.ignoreForbidden && targ.thing.IsForbidden( toil.actor )) )
        				return true;
        		}
                
        		return false;
		    }
        );
		return toil;
	}
}}

